home *** CD-ROM | disk | FTP | other *** search
/ EnigmA Amiga Run 1996 March / EnigmA AMIGA RUN 05 (1996)(G.R. Edizioni)(IT)[!][issue 1996-03][Skylink CD IV].iso / earcd / comm2 / amislt14.lha / AmiSlate / SlateRexx / backgammon.rexx next >
OS/2 REXX Batch file  |  1996-01-27  |  33KB  |  1,171 lines

  1. /* Backgammon for AmiSlate v1.1! */
  2. /* This program should be run on a screen with at least 8 colors */
  3.  
  4. /* v1.1 : fixed a glitch that would sometimes leave a little garbage on
  5.           the screen after a spike redraw. */
  6.           
  7. /* Get our host's name--always given as first argument when run from Amislate */
  8. parse arg CommandPort ActiveString
  9.  
  10. if (length(CommandPort) == 0) then do
  11.     say ""
  12.     say "Usage:  rx backgammon.rexx <REXXPORTNAME>"
  13.     say "        (REXXPORTNAME is usually AMISLATE)"
  14.     say ""
  15.     say "Or run from the Rexx menu within AmiSlate."
  16.     say ""
  17.     exit 0
  18.     end
  19.     
  20. /* Send all commands to this host */
  21. address (CommandPort) 
  22. options results
  23.  
  24. lock ON
  25. lockpalette ON
  26.  
  27. /* See if we're connected */
  28. GetRemoteStateAttrs stem rstateattrs.
  29. if (rstateattrs.mode > -1) then do
  30.         /* Parse command line argument to see if we've been activated by 
  31.            a remote request or a local user */
  32.         check = upper(left(ActiveString,3))
  33.         if (upper(left(ActiveString,3)) ~= 'RE') then 
  34.             GlobData.localplayer = 1
  35.         else
  36.             GlobData.localplayer = -1
  37.         end
  38.     else do
  39.         GlobData.localplayer = 0    /* i.e. we're both players */
  40.         end
  41.         
  42. if (GlobData.localplayer > 0) then do
  43.     call SetStatus("Requesting game from remote user... please wait.")
  44.     RemoteRexxCommand '"'||"Would you like to play backgammon?"||'"' "slaterexx:backgammon.rexx"
  45.     
  46.         waitevent stem handshake. MESSAGE
  47.         if (handshake.message == 0) then 
  48.         do
  49.             call SetStatus("Backgammon Game Refused")
  50.             lock off
  51.             exit 0
  52.         end
  53.     end
  54.  
  55. call SetStatus("Beginning Backgammon...")
  56.  
  57. call ResetGameState
  58. call SetGlobalData
  59. if (GlobData.localplayer >= 0) then call DrawBoard
  60.  
  61. call NextTurn
  62. call HandleEvents
  63.  
  64. lock OFF
  65. exit 0
  66.  
  67. /* Global Data structure:
  68.  
  69.     GlobData.Xspace.[step] (int)     : horizontal pixel offsets to the center of each coord (0-16)
  70.     GlobData.XSize    (int)         : total width of each element
  71.     GlobData.Yspace.[step] (int)     : vertical pixel offsets to the center of each coord (0-16)
  72.     GlobData.YSize    (int)         : total height of each element
  73.  
  74.     (board state)
  75.         GlobData.slots.[slotnum]         : array of slotstates (0..23) (int, > 0 = Pl1 pieces, < 0 = Pl-1 pieces)
  76.                      : note: slot -1 is player 1's "out" pile, and slot 24 is player -1's "out" pile.
  77.     GlobData.die.[dienum]            : the current number on each of the four die.  Usually only 0 & 1 are used,            
  78.  
  79.     (game state)
  80.     GlobData.turn             : whose turn it is
  81.     GlobData.exited.[playernum]      : number of pieces that have been taken off the board for player [playernum]
  82.     GlobData.localplayer              : which player is on local machine; 0 if both are
  83.      (color info)
  84.     GlobData.PieceColor.[playernum]  : color of pieces for this player
  85.     GlobData.SpikeColor.[playernum]  : spike color, alternates
  86.  
  87. */
  88.  
  89. /* --------------------------------------------------------------- */
  90. /* procedure HandleEvents                        */
  91. /* --------------------------------------------------------------- */
  92. HandleEvents: procedure expose GlobData. AMessage.
  93.  
  94. SetSquare = -10
  95. BReversed = 0
  96. BNeedsToRoll = 1
  97. BMustForfeit = 0
  98.  
  99. AMessage.TIMEOUT     = 1    /* No events occurred in specified time period */
  100. AMessage.MESSAGE     = 2    /* Message recieved from remote Amiga */
  101. AMessage.MOUSEDOWN   = 4    /* Left mouse button press in drawing area */
  102. AMessage.MOUSEUP     = 8    /* Left mouse button release in drawing area */
  103. AMessage.RESIZE      = 16    /* Window was resized--time to redraw screen? */ 
  104. AMessage.QUIT        = 32    /* AmiSlate is shutting down */
  105. AMessage.CONNECT     = 64    /* Connection established */
  106. AMessage.DISCONNECT  = 128    /* Connection broken */
  107. AMessage.TOOLSELECT  = 256    /* Tool Selected */
  108. AMessage.COLORSELECT = 512    /* Palette Color selected */
  109. AMessage.KEYPRESS    = 1024    /* Key pressed */
  110. AMessage.MOUSEMOVE   = 2048     /* Mouse was moved */
  111.  
  112. /* Turn on help */
  113. BHelp = 1
  114.  
  115. do while(1)
  116.     waitevent stem event. RESIZE MOUSEDOWN MOUSEUP TOOLSELECT MESSAGE DISCONNECT
  117.  
  118.     if (event.type == AMessage.QUIT) then exit 0
  119.     
  120.     if (event.type == AMessage.DISCONNECT) then do
  121.         call SetStatus("Connection broken--both players now local.")
  122.         GlobData.localplayer = 0
  123.         end
  124.    
  125.     if (event.type == AMessage.MESSAGE) then do
  126.         /* parse the message */
  127.         if (left(event.message,1) = "G") then do
  128.             /* It's our move! */
  129.         BNeedsToRoll = 1
  130.         BMustForfeit = 0
  131.         call NextTurn
  132.         end
  133.     else do
  134.         parse var event.message rfrom rto
  135.         call MovePiece((rfrom+0), (rto+0), 0)  /* update our internals--the (+0) forces the vars back into numeric format */
  136.         end
  137.     end
  138.  
  139.     if (event.type == AMessage.RESIZE) then do
  140.         if ((GlobData.localplayer == GlobData.turn)|(GlobData.localplayer == 0)) then do
  141.             call SetGlobalData
  142.             call DrawBoard
  143.             if (SetSquare ~= -10) then do
  144.                 call HiliteTopPiece(SetSquare)
  145.                 if (BHelp == 1) then call ShowMoves(whatclicked)
  146.                 end
  147.             if (BMustForfeit == 1) then call ShowCantMove
  148.         end
  149.         else do
  150.             /* else just update our internal vars */
  151.             call SetGlobalData
  152.         end
  153.         /* call UpdateStatus */
  154.     end
  155.     
  156.     if ((event.type == AMessage.MOUSEDOWN)&((GlobData.turn = GlobData.localplayer)|(GlobData.localplayer == 0))) then do
  157.         whatclicked = ParseMouseClick(event.x, event.y)
  158.     if (SetSquare == -10) then do
  159.         if (whatclicked == 25) then 
  160.             if (BNeedsToRoll == 1) then do
  161.                 call Roll
  162.                 BNeedsToRoll = 0
  163.                 /* Check to see if we can move */
  164.                 can = CanMove()
  165.                 if (can == 0) then do
  166.                     call ShowCantMove
  167.                     BMustForfeit = 1
  168.                     end
  169.                 end
  170.             else do
  171.                 if (BMustForfeit == 1) then do
  172.                     /* Cancels rest of turn! */
  173.                     BNeedsToRoll = 1
  174.                     BMustForfeit = 0
  175.                     if (localplayer ~= 0) then sendmessage G
  176.                     call NextTurn
  177.                     end
  178.                 end
  179.         else 
  180.         /* Nothing was clicked before, mark this square? */
  181.         if ((BMustForfeit = 0)&(BNeedsToRoll = 0)&(whatclicked < 25)&((GlobData.slots.whatclicked * GlobData.turn) > 0)) then do
  182.             if (PieceCanMove(whatclicked) == 1) then do
  183.                 call HiliteTopPiece(whatclicked)
  184.                 if (BHelp == 1) then call ShowMoves(whatclicked)
  185.                 BReversed = ~BReversed
  186.                 SetSquare = whatclicked
  187.                 end
  188.                 else DisplayBeep LOCAL
  189.             end
  190.         end
  191.     else do
  192.         /* Already had one clicked, now either cancel it or move? */
  193.         if (whatclicked == SetSquare) then do
  194.             /* cancel the piece-move! */
  195.             if (BReversed == 1) then call HiliteTopPiece(whatclicked)
  196.             if (BHelp == 1) then call ClearArrows
  197.             BReversed = 0
  198.             SetSquare = -10
  199.             end
  200.         else do
  201.             dietouse = MoveOkay(SetSquare, whatclicked,1) 
  202.             if (dietouse > -1) then do
  203.                 if (BHelp == 1) then call ClearArrows
  204.                 call MovePiece(SetSquare, whatclicked, 1)
  205.                 BReversed = 0
  206.                 SetSquare = -10
  207.                 GlobData.die.dietouse = 0
  208.                 call DrawDie(dietouse)
  209.                 if ((GlobData.die.0 == 0)&(GlobData.die.1 == 0)&(GlobData.die.2 == 0)&(GlobData.die.3 == 0)) then do
  210.                     BNeedsToRoll = 1
  211.                     BMustForfeit = 0
  212.                     if (localplayer ~= 0) then sendmessage G
  213.                     call NextTurn
  214.                     end
  215.                 else do
  216.                     /* Check to see if we can still move */
  217.                     can = CanMove()
  218.                     if (can == 0) then do
  219.                         call ShowCantMove
  220.                         BMustForfeit = 1
  221.                         end
  222.                     end
  223.                 end
  224.                 else DisplayBeep LOCAL
  225.             end
  226.         end
  227.     end
  228.     end
  229.     return 1
  230.  
  231.  
  232. /* --------------------------------------------------------------- */
  233. /* procedure NextTurn                           */
  234. /*                                    */
  235. /* --------------------------------------------------------------- */
  236. NextTurn: procedure expose GlobData.
  237.     GlobData.Turn = -GlobData.Turn
  238.     do i = 0 to 3 
  239.         GlobData.die.i = 0
  240.         end
  241.  
  242.     /* local game code */
  243.     if (Globdata.localplayer == 0) then do
  244.         call DrawCup(0)
  245.         if (GlobData.turn == 1) then 
  246.             call SetStatus("Player 1, it's your turn to roll.")
  247.         else
  248.             call SetStatus("Player 2, it's your turn to roll.")
  249.         return 1
  250.         end
  251.     
  252.     /* two machine game code */
  253.     if (GlobData.turn == GlobData.localplayer) then do
  254.         call DrawCup(0)
  255.         if (GlobData.turn == 1) then 
  256.             call SetStatus("Player 1, it's your turn to roll.")
  257.         else
  258.             call SetStatus("Player 2, it's your turn to roll.")
  259.         end
  260.     else do
  261.         call SetStatus("Wait for other player to move.")
  262.         end
  263.     return 1
  264.     
  265.         
  266.  
  267. /* --------------------------------------------------------------- */
  268. /* procedure Roll                           */
  269. /*                                    */
  270. /* --------------------------------------------------------------- */
  271. Roll: procedure expose GlobData.
  272.  
  273.     /* First clear the die area */
  274.     cturn = GlobData.turn
  275.     SetFPen GlobData.PieceColor.cturn
  276.     square GlobData.XSpace.7+1 GlobData.YSpace.6+1 GlobData.XSpace.9-1 GlobData.YSpace.10-1 FILL
  277.  
  278.     GlobData.die.0 = random(1,6,time('s'))
  279.     GlobData.die.1 = random(1,6,time('s')+500)
  280.     
  281.     call DrawDie(0)
  282.     call DrawDie(1)
  283.     
  284.     if (GlobData.die.0 == GlobData.die.1) then do
  285.         call SetStatus("You rolled a double!")
  286.  
  287.         GlobData.die.2 = GlobData.die.1
  288.         GlobData.die.3 = GlobData.die.1
  289.     
  290.         call DrawDie(2)
  291.         call DrawDie(3)    
  292.         end
  293.     else do
  294.         if (GlobData.turn == 1) then 
  295.             call SetStatus("Player 1, move your pieces")
  296.         else
  297.             call SetStatus("Player 2, move your pieces")
  298.         end
  299.     return 1        
  300.  
  301.     
  302.  
  303. /* --------------------------------------------------------------- */
  304. /* procedure MovePiece                           */
  305. /*                                    */
  306. /* --------------------------------------------------------------- */
  307. MovePiece: procedure expose GlobData.
  308.     parse arg from, to, showgfx
  309.         
  310.     origfrom = GlobData.slots.from
  311.     negone = -1
  312.     
  313.     /* If it's a two machine game, tell the other machine what we're doing */
  314.     if ((showgfx == 1)&(GlobData.localplayer ~= 0)) then call TransmitMove(from, to)
  315.     
  316.     /* First remove the piece from the old spike and redraw it */
  317.     call RemovePiece(from, GlobData.turn, showgfx)
  318.  
  319.     /* If the piece is moving "off the board", count it up */
  320.     if (to == 24) then do
  321.         /* Piece exited the board for player 1 */
  322.         GlobData.exited.1 = (GlobData.exited.1) + 1
  323.         call CheckForWin
  324.         return 1
  325.         end
  326.     else if (to == -1) then do
  327.         /* Piece exited the board for player 1 */
  328.         GlobData.exited.negone = (GlobData.exited.negone) + 1
  329.         call CheckForWin
  330.         return 1
  331.         end        
  332.  
  333.     /* Now add/replace our piece to the to spike */
  334.     if ((origfrom * GlobData.slots.to) < 0) then do
  335.         /* a piece got killed! */
  336.         call SetStatus("Bump!  Ahahahahaha!!!!")
  337.         if (GlobData.localplayer ~= 0) then DisplayBeep REMOTE
  338.         if (GlobData.slots.to < 0) then 
  339.             call AddPiece(24, -1, showgfx)
  340.         else 
  341.             call AddPiece(-1, 1, showgfx)
  342.             
  343.         GlobData.slots.to = 0
  344.         end
  345.  
  346.     call AddPiece(to, GlobData.turn, showgfx)
  347.     return 1
  348.  
  349.  
  350.  
  351. /* --------------------------------------------------------------- */
  352. /* procedure RemovePiece                       */
  353. /* --------------------------------------------------------------- */
  354. RemovePiece: procedure expose GlobData.    
  355.     parse arg from, which, showgfx
  356.     
  357.     GlobData.slots.from = GlobData.slots.from - which    
  358.     if (showgfx == 1) then call DrawSpike(from)
  359.     return 1
  360.     
  361.     
  362. /* --------------------------------------------------------------- */
  363. /* procedure AddPiece                           */
  364. /* --------------------------------------------------------------- */
  365. AddPiece: procedure expose GlobData.
  366.     parse arg to, which, showgfx
  367.     
  368.     GlobData.slots.to = GlobData.slots.to + which
  369.     if (showgfx == 1) then call DrawPiece(to, GlobData.PieceColor.which, abs(GlobData.slots.to), 0)
  370.     return 1
  371.     
  372.  
  373. /* --------------------------------------------------------------- */
  374. /* procedure MoveOkay                           */
  375. /*                                    */
  376. /* returns the index of the die to use if the move is acceptable,  */
  377. /* or -1 if it is illegal.                        */
  378. /*                                    */
  379. /* --------------------------------------------------------------- */
  380. MoveOkay: procedure expose GlobData.
  381.     parse arg from, to, CountExits
  382.  
  383.     negone = -1
  384.     numsquares = abs(to-from)
  385.         
  386.     /* Side specific rules! */
  387.     if (GlobData.slots.from < 0) then do
  388.         /* no moving backwards */
  389.         if (to > from) then return -1
  390.  
  391.         /* no moving anyone else if we have a guy "out" */
  392.         if ((GlobData.slots.24 ~= 0)&(from ~= 24)) then return -1
  393.  
  394.         /* No moves into the "out" unless all of our pieces are (exited or in the last quad) */
  395.         /* Also, we can't exit on a roll larger than necessary *unless* there is no other use for that roll. */
  396.         if (to <= -1) then do
  397.             disttoexit = from + 1
  398.  
  399.             /* If we're not checking for exits, return failure now */
  400.             if (CountExits == 0) then do
  401.                 /* Still need to watch for an exact out! */
  402.                 do checkdie = 0 to 3 
  403.                     if (GlobData.die.checkdie == disttoexit) then 
  404.                         if (CanMoveOut(1) == 1) then return checkdie
  405.                     end                
  406.                 return -1
  407.                 end                
  408.             
  409.             /* Otherwise check to see if we can move out */
  410.             if (CanMoveOut(-1) == 0) then return -1
  411.             else do
  412.                 do checkdie = 0 to 3 
  413.                     if (GlobData.die.checkdie == disttoexit) then return checkdie
  414.                     end
  415.                 do checkdie = 0 to 3
  416.                     if (GlobData.die.checkdie > disttoexit) then do
  417.                         if (CanExitOnExtra(GlobData.die.checkdie) == 1) then return checkdie
  418.                         end
  419.                     end
  420.                 /* default: can't move out */
  421.                 return -1
  422.                 end
  423.             end
  424.         end
  425.     else do
  426.         /* no moving backwards */
  427.         if (to < from) then return -1
  428.  
  429.         /* no moving anyone else if we have a guy "out" */
  430.         if ((GlobData.slots.negone ~= 0)&(from ~= -1)) then return -1
  431.  
  432.         /* No moves into the "out" unless all of our pieces are (exited or in the last quad) */
  433.         if (to >= 24) then do
  434.             disttoexit = 24 - from
  435.  
  436.             /* If we're not checking for exits, return failure now */
  437.             if (CountExits == 0) then do
  438.                 /* Still need to watch for an exact out! */
  439.                 do checkdie = 0 to 3 
  440.                     if (GlobData.die.checkdie == disttoexit) then 
  441.                         if (CanMoveOut(1) == 1) then return checkdie
  442.                     end                
  443.                 return -1
  444.                 end
  445.                 
  446.             /* Otherwise check to see if we can move out */
  447.             if (CanMoveOut(1) == 0) then 
  448.                 return -1 
  449.             else do
  450.                 do checkdie = 0 to 3
  451.                     if (GlobData.die.checkdie == disttoexit) then return checkdie
  452.                     end
  453.                 do checkdie = 0 to 3
  454.                     if (GlobData.die.checkdie > disttoexit) then do
  455.                         if (CanExitOnExtra(GlobData.die.checkdie) == 1) then return checkdie
  456.                         end
  457.                     end
  458.                 /* default: can't move out */
  459.                 return -1
  460.                 end
  461.             end
  462.         end
  463.     
  464.     /* first get this into absolute numbers */
  465.     if (GlobData.slots.from < 0) then do
  466.         fromsq = -(GlobData.slots.from)
  467.         tosq   = -(GlobData.slots.to)
  468.         end
  469.     else do
  470.         fromsq = GlobData.slots.from
  471.         tosq   = GlobData.slots.to
  472.         end
  473.     
  474.     /* At this point: fromsq > 0 */
  475.     if (tosq < -1) then return -1
  476.     
  477.     /* Make sure this move is available on one of the die */
  478.     diefound = -1
  479.     do i = 0 to 3
  480.         if (GlobData.die.i == numsquares) then diefound = i
  481.         end
  482.     
  483.     return diefound
  484.     
  485.     
  486.     
  487.  
  488. /* --------------------------------------------------------------- */
  489. /* procedure HiliteTopPiece                       */
  490. /* --------------------------------------------------------------- */
  491. HiliteTopPiece: procedure expose GlobData.
  492.     parse arg spikenum
  493.  
  494.     cturn = GlobData.turn
  495.     
  496.     call DrawPiece(spikenum, GlobData.PieceColor.cturn, abs(GlobData.slots.spikenum), 1)
  497.     return 1
  498.  
  499.  
  500.  
  501. /* --------------------------------------------------------------- */
  502. /* procedure ParseMouseClick                        */
  503. /*                                   */
  504. /* returns (-1)-(24) if a stack was clicked, or 25 if the center   */
  505. /* dice/cup area was clicked                        */
  506. /*                                   */
  507. /* --------------------------------------------------------------- */
  508. ParseMouseClick: procedure expose GlobData.
  509.     parse arg xp, yp
  510.     
  511.     /* figure out vertical zone */
  512.     yZone = 1    /* default */
  513.     if (yp < GlobData.YSpace.6) then yZone = 0
  514.     if (yp > GlobData.YSpace.10) then yZone = 2
  515.     
  516.     /* figure out horizontal zone */
  517.     xZone = 1    /* default */
  518.     if (xp < GlobData.XSpace.7) then xZone = 0
  519.     if (xp > GlobData.XSpace.9) then xZone = 2
  520.     
  521.     /* are we in the dice/cup zone? */
  522.     if (xZone == 1) then do
  523.        if (yZone == 0) then return -1
  524.        if (yZone == 1) then return 25
  525.        return 24
  526.        end
  527.     else do
  528.        /* force into yzone 0 or 2 */
  529.        if (yp < GlobData.YSpace.8) then 
  530.            yzone = 0 
  531.        else 
  532.            yzone = 2
  533.        end
  534.        
  535.     /* Must be a regular stack--get the horizontal offset */    
  536.     if (xZone == 0) then offset = (xp-(GlobData.XSize/2)) / GlobData.XSize         
  537.     if (xZone == 2) then offset = ((xp-GlobData.XSpace.9-(GlobData.XSize/2)) / GlobData.XSize)
  538.     
  539.     offset = trunc(offset)    
  540.     if (offset < 0) then offset = 0
  541.     if (offset > 5) then offset = 5
  542.  
  543.     if ((xZone = 0)&(yZone = 0)) then return 11-offset
  544.     if ((xZone = 2)&(yZone = 0)) then return 5-offset
  545.     if ((xZone = 0)&(yZone = 2)) then return 12+offset
  546.     if ((xZone = 2)&(yZone = 2)) then return 18+offset
  547.     return -55
  548.  
  549.  
  550. /* --------------------------------------------------------------- */
  551. /* procedure ResetGameState                       */
  552. /* --------------------------------------------------------------- */
  553. ResetGameState: procedure expose GlobData.
  554.     negone = -1;
  555.     
  556.     DO i = -1 to 24
  557.         GlobData.slots.i = 0    /* first clear the board */
  558.     end
  559.  
  560.     /* initial board config */
  561.     GlobData.slots.0 = 2
  562.     GlobData.slots.5 = -5
  563.     GlobData.slots.7 = -3
  564.     GlobData.slots.11 = 5
  565.     GlobData.slots.12 = -5
  566.     GlobData.slots.16 = 3
  567.     GlobData.slots.18 = 5
  568.     GlobData.slots.23 = -2
  569.  
  570.  
  571. /* Setup to test end game -- dont use
  572. GlobData.slots.6 = -1
  573. GlobData.slots.5 = -2
  574. GlobData.slots.4 = -2
  575. GlobData.slots.3 = -5
  576. GlobData.slots.2 = -1
  577. GlobData.slots.1 = -3
  578. GlobData.slots.0 = -1
  579.  
  580. GlobData.slots.15 = 1
  581. GlobData.slots.18 = 2
  582. GlobData.slots.19 = 2
  583. GlobData.slots.20 = 5
  584. GlobData.slots.21 = 1
  585. GlobData.slots.22 = 3
  586. GlobData.slots.23 = 1
  587. */
  588.  
  589.     GlobData.slots.negone = 0
  590.     GlobData.slots.24 = 0
  591.  
  592.     GlobData.out.1 = 0
  593.     GlobData.out.negone = 0
  594.  
  595.     GlobData.die.0 = 0
  596.     GlobData.die.1 = 0
  597.     GlobData.die.2 = 0
  598.     GlobData.die.3 = 0
  599.  
  600.     GlobData.exited.1 = 0
  601.     GlobData.exited.negone = 0
  602.     
  603.     GlobData.turn    = -1;
  604.  
  605.     return 1
  606.  
  607.  
  608. /* --------------------------------------------------------------- */
  609. /* Sets the appropriate palette positions to nice colors           */
  610. /* --------------------------------------------------------------- */
  611. SetColors: procedure expose GlobData.
  612.     negone = -1
  613.  
  614.     SetPenColor 0 10 10 10
  615.     SetPenColor 1 0 0 0
  616.     SetPenColor 2 15 15 15
  617.     /* SetPenColor 3 10 10 15 -- not used anyway */
  618.         
  619.     SetPenColor GlobData.SpikeColor.1      14 14 14    /* Bright blue */
  620.     SetPenColor GlobData.SpikeColor.negone 15 10 08  /* Gray-blue */
  621.  
  622.     SetPenColor GlobData.PieceColor.1      06 08 15   /* Bright red */
  623.     SetPenColor GlobData.PieceColor.negone 15 05 05   /* Gray-red */
  624.     return 1
  625.     
  626.     
  627. /* --------------------------------------------------------------- */
  628. /* procedure SetGlobalData                       */
  629. /* --------------------------------------------------------------- */
  630. SetGlobalData: procedure expose GlobData.
  631.     negone = -1
  632.     
  633.     /* Check to see whether we are connected */
  634.        GetWindowAttrs stem winattrs.
  635.        BoardWidth = winattrs.width  - 58
  636.        BoardHeight= winattrs.height - 55
  637.  
  638.     /* Set up offsets */
  639.     fStep = 1/16
  640.     DO i=0 to 16 
  641.       GlobData.Xspace.i = trunc(BoardWidth * (fStep * i))
  642.       GlobData.Yspace.i = trunc(BoardHeight * (fStep * i))
  643.       end
  644.  
  645.     GlobData.XSize = BoardWidth % 16
  646.     GlobData.YSize = BoardHeight % 16
  647.  
  648.        if (winattrs.depth < 3) then do
  649.         EasyRequest BackGammon_Error '"'||"You need at least a 8-color screen to play backgammon!"||'"' '"'||"Abort Backgammon"||'"'
  650.         call SetStatus("Backgammon game exited.")
  651.         lock off
  652.         exit 0
  653.         end 
  654.  
  655.     GlobData.SpikeColor.1 = 4
  656.     GlobData.SpikeColor.negone = 5
  657.     GlobData.PieceColor.1 = 6
  658.     GlobData.PieceColor.negone = 7
  659.     return 1
  660.  
  661.  
  662. /* --------------------------------------------------------------- */
  663. /* procedure DrawBoard                                             */
  664. /* --------------------------------------------------------------- */
  665. DrawBoard: procedure expose GlobData.
  666.     SetFColor 0 0 0        /* Get a black pen */
  667.  
  668.     Clear
  669.  
  670.     SetWindowTitle '"' || "Hang on, drawing the board..." || '"'
  671.  
  672.     call SetColors
  673.  
  674.     SetFPen 1
  675.     square GlobData.Xspace.0 GlobData.Yspace.0 GlobData.XSpace.16 GlobData.YSpace.16
  676.     Do i = -1 to 24
  677.         call DrawSpike(i)
  678.         end
  679.  
  680.     SetFPen 1
  681.     line GlobData.Xspace.7 GlobData.Yspace.0 GlobData.XSpace.7 GlobData.YSpace.16
  682.     line GlobData.Xspace.9 GlobData.Yspace.0 GlobData.XSpace.9 GlobData.YSpace.16
  683.     square GlobData.XSpace.7 GlobData.YSpace.6 GlobData.XSpace.9 GlobData.YSpace.10    
  684.         
  685.     if ((GlobData.die.0 + GlobData.die.1 + GlobData.die.2 + GlobData.die.3) > 0)  then do
  686.         cturn = GlobData.turn
  687.         bgcol = GlobData.PieceColor.cturn
  688.         SetFPen bgcol
  689.         square GlobData.XSpace.7+1 GlobData.YSpace.6+1 GlobData.XSpace.9-1 GlobData.YSpace.10-1 FILL
  690.         Do i = 0 to 3 
  691.             call DrawDie(i)
  692.             end
  693.         end
  694.     else call DrawCup(0)
  695.  
  696.     call SetStatus("_LAST")
  697.     return 1
  698.  
  699.  
  700. /* --------------------------------------------------------------- */
  701. /* procedure DrawSpike                                             */
  702. /* --------------------------------------------------------------- */
  703. DrawSpike: procedure expose GlobData.
  704.     parse arg spikenum
  705.  
  706.     negone = -1
  707.  
  708.     /* Figure out what color to draw the spike as */
  709.     spikecol = GlobData.SpikeColor.1
  710.     if ((spikenum // 2) == 1) then spikecol = GlobData.SpikeColor.negone
  711.  
  712.     /* Figure out what color to draw any pieces as */
  713.     piececol = GlobData.PieceColor.1
  714.     numpieces  = GlobData.slots.spikenum
  715.     if (numpieces < 0) then do
  716.         piececol = GlobData.PieceColor.negone
  717.         numpieces = -numpieces
  718.         end
  719.  
  720.     /* Figure out our coords */
  721.     baseofspike = getSpikeBase(spikenum)
  722.     topofspike = getSpikeTop(spikenum)
  723.     spikecenter = getSpikecenter(spikenum)
  724.  
  725.     /* First blank out the area we are to draw on */
  726.     wid = GlobData.XSize % 3
  727.     BaseOfSpikeCoord = GlobData.YSpace.baseofspike + getSpikeDir(spikenum)
  728.     TopOfSpikeCoord = GlobData.Yspace.topofspike 
  729.     if ((spikenum == -1)|(spikenum == 24)) then TopOfSpikeCoord = TopOfSpikeCoord - getSpikeDir(spikenum)
  730.     /* extra is the few more pixels on the left we add in to ensure that any extra layers are erased */
  731.     extra = trunc((trunc(abs((GlobData.slots.spikenum)/5))+1)*(GlobData.XSize/10))
  732.     SetFPen 0    
  733.     square (GlobData.Xspace.spikecenter)-wid-extra BaseOfSpikeCoord (GlobData.Xspace.spikecenter)+wid TopOfSpikeCoord FILL
  734.  
  735.     SetFPen 1
  736.     if ((spikenum >= 0) & (spikenum <= 23)) then do
  737.       /* Draw the spike */
  738.       line (GlobData.Xspace.spikecenter)-wid BaseOfSpikeCoord GlobData.Xspace.spikecenter TopOfSpikeCoord
  739.       line (GlobData.Xspace.spikecenter)+wid BaseOfSpikeCoord GlobData.Xspace.spikecenter TopOfSpikeCoord
  740.  
  741.       /* Redraw the baseline just to make sure the flood doesn't escape */
  742.       line (GlobData.Xspace.spikecenter)+wid GlobData.YSpace.baseofspike (GlobData.Xspace.spikecenter)-wid GlobData.YSpace.baseofspike
  743.         
  744.       /* calculate circle coords */
  745.       cx = GlobData.XSize % 4
  746.       cy = GlobData.YSize % 2
  747.       
  748.       SetFPen spikecol    
  749.       flood GlobData.Xspace.spikecenter ((BaseOfSpikeCoord + TopOfSpikeCoord)%2)
  750.       circle GlobData.Xspace.spikecenter TopOfSpikeCoord cx cy FILL
  751.       SetFPen 1
  752.       circle GlobData.Xspace.spikecenter TopOfSpikeCoord cx cy 
  753.       end
  754.  
  755.     /* Draw pieces if any */
  756.     if (numpieces > 0) then do 
  757.         do i=1 to numpieces 
  758.             call DrawPiece(spikenum, piececol, i, 0) 
  759.             end
  760.         end
  761.     return 1    
  762.  
  763.  
  764. /* Draws a piece on the spikenum spike, in pen piecepen, at position piecenum (where 0 is the 
  765.    base of the spike, and 5 is the top */
  766. DrawPiece: procedure expose GlobData.
  767.     parse arg spikenum, piecepen, piecenum, BXor
  768.  
  769.     dx = 0
  770.     dy = 0
  771.     
  772.     /* Additional rows if piecenum is > 5 */
  773.     do while (piecenum > 5)
  774.         piecenum = piecenum - 5
  775.         dx = dx - GlobData.XSize/10
  776.         dy = dy - GlobData.YSize/10
  777.         end
  778.  
  779.     dx = trunc(dx)
  780.     dy = trunc(dy)
  781.     
  782.     cx = getSpikeCenter(spikenum)
  783.     cy = getSpikeBase(spikenum) + (getSpikeDir(spikenum) * piecenum)
  784.  
  785.     rx = GlobData.XSize % 3
  786.     ry = GlobData.YSize % 2
  787.  
  788.  
  789.     if (BXor == 0) then do
  790.         SetFPen piecepen
  791.         circle (GlobData.Xspace.cx + dx) (GlobData.Yspace.cy + dy) rx ry FILL
  792.         SetFPen 1
  793.         circle (GlobData.Xspace.cx + dx) (GlobData.Yspace.cy + dy) rx ry 
  794.         end
  795.     else circle (GlobData.Xspace.cx + dx) (GlobData.Yspace.cy + dy) rx ry FILL XOR
  796.     
  797.     return 1
  798.  
  799.  
  800. /* Draws a cup indicating that a player may roll.  The cup is drawn with the color
  801.    indicated by (cupcolor) */
  802. DrawCup: procedure expose GlobData.
  803.     parse arg BXor 
  804.     
  805.     negone = -1
  806.  
  807.     left =  (GlobData.XSpace.7 + GlobData.XSpace.8)%2
  808.     right = (GlobData.XSpace.8 + GlobData.XSpace.9)%2
  809.  
  810.     top =    (GlobData.YSpace.6 + GlobData.YSpace.7)%2
  811.     bottom = (GlobData.YSpace.9 + GlobData.YSpace.10)%2
  812.     
  813.     if (BXor == 1) then do
  814.         square left top right bottom XOR FILL
  815.         return 1
  816.         end
  817.     
  818.     /* Clear the whole area */
  819.     SetFPen 0
  820.     square GlobData.XSpace.7+1 GlobData.YSpace.6+1 GlobData.XSpace.9-1 GlobData.YSpace.10-1 FILL
  821.  
  822.     ctr = (right+left)%2
  823.     midht  = GlobData.YSize%2
  824.  
  825.     SetFPen 1
  826.     circle ctr top+midht ((right-left)%2) midht FILL
  827.     line left top+midht left bottom-midht
  828.     line right top+midht right bottom-midht
  829.     circle ctr bottom-midht ((right-left)%2) midht
  830.             
  831.     /* Fill it in */
  832.     currentturn = GlobData.turn
  833.     setFPen GlobData.PieceColor.currentturn
  834.     square left+1 top+midht+midht+2 right-1 bottom-midht FILL    
  835.     flood ctr bottom-midht+1
  836.     flood ctr top+midht+midht+1 
  837.     return 1
  838.  
  839.  
  840.  
  841. /* Draws dice number (dieindex) to show the number indicated in GlobData.die.(dieindex) */
  842. DrawDie: procedure expose GlobData.
  843.     parse arg dieindex
  844.  
  845.     left = 7
  846.     top = 7
  847.     if (dieindex >= 2) then top = top + 1
  848.     if (dieindex == 1) then left = left + 1
  849.     if (dieindex == 3) then left = left + 1
  850.  
  851.     /* convert to "real" coordinates */
  852.     left = GlobData.Xspace.left
  853.     top  = GlobData.Yspace.top
  854.  
  855.     /* shrink it 25% */
  856.     width = trunc(GlobData.XSize * 0.75)
  857.     height = trunc(GlobData.YSize * 0.75)
  858.  
  859.     /* and recenter it by moving it down&right 12.5% */
  860.     left = left + trunc(GlobData.XSize * 0.125)
  861.     top  = top  + trunc(GlobData.YSize * 0.125)
  862.  
  863.     /* now calculate the right and bottom edges */
  864.     right = left + width
  865.     bottom = top + height
  866.  
  867.     /* number to show on the die */
  868.     num = GlobData.die.dieindex
  869.  
  870.     /* if it's unset, just erase the area */
  871.     if (num < 1) then do
  872.         /* Calculate background color = player's piece color */
  873.         cturn = GlobData.turn
  874.         SetFPen GlobData.PieceColor.cturn
  875.         square left top right bottom FILL
  876.         return 1
  877.         end
  878.  
  879.     /* start with blank die */
  880.     SetFColor 15 15 15
  881.     square left top right bottom FILL
  882.     SetFPen 1
  883.     square left top right bottom 
  884.  
  885.     dotrx = width%12        /* radii of dot */
  886.     dotry = height%12
  887.     dx = width % 4        /* offset from center dot */
  888.     dy = height % 4
  889.  
  890.     x2 = (right + left)%2
  891.     y2 = (bottom + top)%2
  892.  
  893.     x1 = x2 - dx
  894.     x3 = x2 + dx
  895.     y1 = y2 - dy
  896.     y3 = y2 + dy
  897.  
  898.     if (num == 1) then do
  899.         circle x2 y2 dotrx dotry FILL
  900.         end        
  901.     else if (num == 2) then do
  902.         circle x1 y1 dotrx dotry FILL
  903.         circle x3 y3 dotrx dotry FILL
  904.         end        
  905.     else if (num == 3) then do
  906.         circle x1 y1 dotrx dotry FILL
  907.         circle x2 y2 dotrx dotry FILL
  908.         circle x3 y3 dotrx dotry FILL
  909.         end        
  910.     else if (num == 4) then do
  911.         circle x1 y1 dotrx dotry FILL
  912.         circle x1 y3 dotrx dotry FILL
  913.         circle x3 y1 dotrx dotry FILL
  914.         circle x3 y3 dotrx dotry FILL
  915.         end        
  916.     else if (num == 5) then do
  917.         circle x1 y1 dotrx dotry FILL
  918.         circle x1 y3 dotrx dotry FILL
  919.         circle x3 y1 dotrx dotry FILL
  920.         circle x3 y3 dotrx dotry FILL
  921.         circle x2 y2 dotrx dotry FILL
  922.         end        
  923.     else if (num == 6) then do
  924.         circle x1 y1 dotrx dotry FILL
  925.         circle x1 y2 dotrx dotry FILL
  926.         circle x1 y3 dotrx dotry FILL
  927.         circle x3 y1 dotrx dotry FILL
  928.         circle x3 y2 dotrx dotry FILL
  929.         circle x3 y3 dotrx dotry FILL
  930.         end        
  931.     return 1
  932.  
  933.  
  934.  
  935. /* Returns the horizontal coordinate number of a spike, given the spike index number */
  936. getSpikeCenter: procedure 
  937.     parse arg spikenum
  938.  
  939.     if (spikenum == -1) then return 8
  940.     if (spikenum == 24) then return 8
  941.  
  942.     if (spikenum <= 5) then do
  943.         /* in the upper right quadrant */
  944.         return 15-spikenum
  945.     end
  946.     else if (spikenum <= 11) then do
  947.         /* in the upper left quadrant */
  948.         return (6-spikenum)+6
  949.     end
  950.     else if (spikenum <= 17) then do
  951.         /* in the lower left quadrant */
  952.         return (spikenum-17)+6
  953.     end
  954.     else do
  955.         /* in the lower right quadrant */
  956.         return (spikenum-18)+10
  957.     end
  958.  
  959. /* returns the base coordinate of a spike */
  960. getSpikeBase: procedure
  961.     parse arg spikenum
  962.  
  963.     if (spikenum > 11) then
  964.         return 16
  965.     else
  966.         return 0
  967.  
  968. /* returns the top coordinate of a spike */
  969. getSpikeTop: procedure
  970.     parse arg spikenum
  971.  
  972.     if (spikenum > 11) then
  973.         return 10
  974.     else
  975.         return 6
  976.  
  977. /* returns 1 if the spike hangs down, else -1 if it pokes up */
  978. getSpikeDir: procedure
  979.     parse arg spikenum
  980.  
  981.     if (spikenum > 11) then do
  982.         return -1
  983.         end
  984.     else do
  985.         return 1
  986.         end
  987.  
  988.  
  989. SetStatus: procedure
  990.     parse arg newstatus
  991.     
  992.     if (newstatus == "_LAST") then do
  993.         SetWindowTitle '"' || getclip("PrevString") || '"'
  994.         end
  995.     else do
  996.         call setclip("PrevString",newstatus)
  997.         SetWindowTitle '"' || newstatus || '"'
  998.     end
  999.     return 1
  1000.     
  1001.  
  1002. /* draws an x over the dice for now */    
  1003. ShowCantMove: procedure expose GlobData.
  1004.     SetFPen 1
  1005.     line GlobData.XSpace.7+1 GlobData.YSpace.6+1 GlobData.XSpace.9-1 GlobData.YSpace.10-1
  1006.     line  GlobData.XSpace.9-1 GlobData.YSpace.6+1 GlobData.XSpace.7+1 GlobData.YSpace.10-1 
  1007.     call SetStatus("Sorry, you can't move!  Click on the dice to continue")
  1008.     return 1
  1009.  
  1010.  
  1011.     
  1012. /* returns 1 if a move is available, else returns 0 */
  1013. CanMove: procedure expose GlobData.
  1014.     do i = -1 to 24
  1015.         if (PieceCanMove(i) == 1) then return 1
  1016.         end                  
  1017.     return 0
  1018.     
  1019.  
  1020. /* returns 1 if the piece on slot i can move, else 0 */
  1021. PieceCanMove: procedure expose GlobData.
  1022.     parse arg from
  1023.     
  1024.     if ((GlobData.slots.from * GlobData.turn) <= 0) then return 0    
  1025.     
  1026.     do d = 0 to 3
  1027.         if (GlobData.die.d > 0) then do
  1028.             if (MoveOkay(from,from+(GlobData.die.d * GlobData.turn),1) ~= -1) then return 1
  1029.             end  
  1030.         end          
  1031.     return 0
  1032.     
  1033.     
  1034. /* returns 1 if a player can move a piece "out" to the given exit, else 0 */
  1035. CanMoveOut: procedure expose GlobData.
  1036.     parse arg playernum
  1037.  
  1038.     sum = GlobData.exited.playernum
  1039.     
  1040.     if (playernum == -1) then 
  1041.         do i = 0 to 5
  1042.             if (GlobData.slots.i < 0) then sum = sum - GlobData.slots.i
  1043.             end
  1044.     else if (playernum == 1) then
  1045.         do i = 18 to 23
  1046.             if (GlobData.slots.i > 0) then sum = sum + GlobData.slots.i
  1047.             end
  1048.             
  1049.     if (sum == 15) then return 1
  1050.     return 0
  1051.  
  1052.             
  1053. /* Draws arrows for possible moves for this piece */        
  1054. ShowMoves: procedure expose GlobData.
  1055.     parse arg from
  1056.     
  1057.     do d = 0 to 3
  1058.         if (GlobData.die.d > 0) then do
  1059.             if (MoveOkay(from,from+(GlobData.die.d * GlobData.Turn),1) ~= -1) then call DrawArrow(from+(GlobData.die.d * GlobData.Turn))
  1060.             end  
  1061.         end          
  1062.     return 1
  1063.  
  1064. /* Draw an arrow pointing to the specified spike */
  1065. DrawArrow: procedure expose GlobData.
  1066.     parse arg spikenum
  1067.     
  1068.     spikenum = ChopRange(spikenum,-1,24)
  1069.     
  1070.     hcoord = getSpikeCenter(spikenum)
  1071.     vcoord = getSpikeTop(spikenum) + getSpikeDir(spikenum)
  1072.  
  1073.     /* Special cases! */
  1074.     if (spikenum = -1) then    vcoord = 5
  1075.     if (spikenum = 24) then vcoord = 11
  1076.         
  1077.     SetFPen 1    
  1078.     circle GlobData.XSpace.hcoord GlobData.YSpace.vcoord GlobData.XSize%10 GlobData.YSize%10 FILL
  1079.     return 1
  1080.     
  1081.  
  1082. /* Clears all arrows from the board */    
  1083. ClearArrows: procedure expose GlobData.
  1084.     
  1085.     SetFPen 0
  1086.     
  1087.     /* right portion! */
  1088.     topofrect     = MidWayBetween(6,7,Y) + 1
  1089.     bottomofrect  = MidWayBetween(9,10,Y) - 1
  1090.     leftofrect    = MidWayBetween(9,10,X) + 1
  1091.     rightofrect   = MidWayBetween(15,16,X) - 1
  1092.     square leftofrect topofrect rightofrect bottomofrect FILL
  1093.     
  1094.     /* left portion! */
  1095.     leftofrect    = MidWayBetween(0,1,X)
  1096.     rightofrect  = MidWayBetween(6,7,X)
  1097.     square leftofrect topofrect rightofrect bottomofrect FILL 
  1098.     
  1099.     /* special cases! */
  1100.     circle GlobData.XSpace.8 GlobData.YSpace.5  GlobData.XSize%10 GlobData.YSize%10 FILL
  1101.     circle GlobData.XSpace.8 GlobData.YSpace.11 GlobData.XSize%10 GlobData.YSize%10 FILL 
  1102.     return 1    
  1103.     
  1104.  
  1105. /* Returns the point midway between two coords */
  1106. MidWayBetween: procedure expose GlobData.
  1107.     parse arg left, right, XorY
  1108.     if (XorY = X) then 
  1109.         return (GlobData.XSpace.left + GlobData.XSpace.right)%2
  1110.     else    
  1111.         return (GlobData.YSpace.left + GlobData.YSpace.right)%2
  1112.  
  1113. ChopRange: procedure
  1114.     parse arg myval, lo, hi
  1115.     if (myval < lo) then return lo
  1116.     if (myval > hi) then return hi
  1117.     return myval
  1118.     
  1119.     
  1120. /* --------------------------------------------------------------- */
  1121. /* procedure CheckForWin                                           */
  1122. /* --------------------------------------------------------------- */
  1123. CheckForWin: procedure expose GlobData.
  1124.     winner = nobody
  1125.     negone = -1
  1126.  
  1127.     if (GlobData.localplayer == 0) then do
  1128.         if (GlobData.exited.1 == 15) then winner = "Player 1"
  1129.         else if (GlobData.exited.negone == 15) then winner = "Player 2"
  1130.         end 
  1131.     else do
  1132.         localplayer = GlobData.localplayer
  1133.         nonlocalplayer = -GlobData.localplayer
  1134.         if (GlobData.exited.localplayer == 15) then winner = "You"
  1135.         else if (GlobData.exited.nonlocalplayer == 15) then winner = "Your opponent"
  1136.         end
  1137.     
  1138.     /* nobody one yet */
  1139.     if (winner == nobody) then return 1
  1140.     
  1141.     EasyRequest "Winner!" '"'||winner||" has won the game!"||'"' "Okay"
  1142.     call SetStatus("Game Over.  Rerun the script to play again.")
  1143.     lock off
  1144.     exit
  1145.     return 0
  1146.     
  1147.         
  1148. /* Transmit our move to our opponent */
  1149. TransmitMove: procedure 
  1150.     parse arg from, to
  1151.     
  1152.     sstring = '"' || from || " " || to || '"'
  1153.     sendmessage sstring
  1154.     return 1
  1155.     
  1156. /* This function determines whether or not a piece may exit using
  1157.    a given die number (that is larger than needed to get to the exit).  
  1158.    It will return 1 iff there is no way to use that same die number
  1159.    to move any other piece anywhere else. */
  1160. CanExitOnExtra: procedure expose GlobData.
  1161.     parse arg dienum
  1162.     
  1163.     /* Only check valid die */
  1164.     if (dienum <= 0) then return 0
  1165.         
  1166.     do i = 0 to 23
  1167.         if (((GlobData.slots.i)*(GlobData.turn)) > 0) then do
  1168.             if (MoveOkay(i, i+(dienum*GlobData.turn), 0) ~= -1) then return 0
  1169.             end
  1170.         end
  1171.     return 1    /* no moves in --> can move out */